// 变量
package main

import (
	"fmt"
	"os"
)

func main() {
	// var声明语句可以创建一个特定类型的变量，然后给变量附加一个名字，并且设置变量的初始值。变量声明的一般语法如下：
	//var 变量名字 类型 = 表达式
	var s string = "hh"
	fmt.Println(s)
	var i, j, k int                 // int int int
	i, j = j, i                     // 元组赋值，元组赋值是另一种形式的赋值语句，它允许同时更新多个变量的值。在赋值之前，赋值语句右边的所有表达式将会先进行求值，然后再统一更新左边对应变量的值。这对于处理有些同时出现在元组赋值语句左右两边的变量很有帮助，例如我们可以这样交换两个变量的值
	var b, f, c = true, 2.3, "four" // bool float64 string
	fmt.Println(i, j, k, b, f, c)
	var r, err = os.Open("ss")
	fmt.Println(r, err)
	// 简短变量声明，它以“名字 := 表达式”形式声明变量，变量的类型根据表达式来自动推导
	// 因为简洁和灵活的特点，简短变量声明被广泛用于大部分的局部变量的声明和初始化。
	// var 形式的声明语句往往是用于需要显式指定变量类型地方。
	kk := 100                 // int
	var boiling float64 = 100 // a float64
	var err1 error
	fmt.Println(kk, boiling, err1)
	/**
		一个变量对应一个保存了变量对应类型值的内存空间。普通变量在声明语句创建时被绑定到一个变量名，比如叫x的变量，但是还有很多变量始终以表达式方式引入，例如x[i]或x.f变量。所有这些表达式一般都是读取一个变量的值，除非它们是出现在赋值语句的左边，这种时候是给对应变量赋予一个新的值。

	一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址，但是对于每一个变量必然有对应的内存地址。通过指针，我们可以直接读或更新对应变量的值，而不需要知道该变量的名字（如果变量有名字的话）。

	如果用“var x int”声明语句声明一个x变量，那么&x表达式（取x变量的内存地址）将产生一个指向该整数变量的指针，指针对应的数据类型是*int，指针被称之为“指向int类型的指针”。如果指针名字为p，那么可以说“p指针指向变量x”，或者说“p指针保存了x变量的内存地址”。同时*p表达式对应p指针指向的变量的值。一般*p表达式读取指针指向的变量的值，这里为int类型的值，同时因为*p对应一个变量，所以该表达式也可以出现在赋值语句的左边，表示更新指针所指向的变量的值。
	*/

	// xx := 1
	// p := &xx
	// fmt.Println(*p)
	// *p = 2
	// fmt.Println(xx)

	var xx, yy int
	fmt.Println(&xx == &xx, &xx == &yy, &xx == nil) // true false false

	var fyy = ff()
	fmt.Println(fyy)

	fmt.Println(ff() == ff()) // false

	v := 1
	incr(&v)                // side effect:v is now 2
	fmt.Println(incr((&v))) // "3" (and v is 3)

	newp := new(int)
	fmt.Println(*newp)
	*newp = 2
	fmt.Println(*newp)

	medals := []string{"gold", "silver", "bronze"}
	fmt.Println(medals[0])

}

// 在Go语言中，返回函数中局部变量的地址也是安全的。例如下面的代码，调用f函数时创建局部变量v，在局部变量地址被返回之后依然有效，因为指针p依然引用这个变量。
func ff() *int {
	v := 1
	return &v
}

func incr(p *int) int {
	*p++ // 非常重要：只是增加p指向的变量的值，并不改变p指针
	return *p
}

//局部变量的声明周期则是动态的：每次从创建一个新变量的声明语句开始，直到该变量不再被引用为止，然后变量的存储空间可能被回收。
//go语言的自动垃圾收集器实现思路：
//从每个包级的变量和每个当前运行函数的每一个局部变量开始，通过指针或引用的访问路径遍历，是否可以找到该变量。如果不存在这样的访问路径，那么说明该变量是不可达的，也就是说它是否存在并不会影响程序后续的计算结果。
//因为一个变量的有效周期只取决于是否可达，因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时，局部变量可能在函数返回之后依然存在。

// 最大公约数
func gcd(x, y int) int {
	for y != 0 {
		x, y = y, x%y
	}
	return x
}

// 计算斐波纳契数列（Fibonacci）的第N个数
func fib(n int) int {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		x, y = y, x+y
	}
	return x
}
